﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Net;
using System.IO;
using Autofac;
using HtmlAgilityPack;
using NCrawler;
using NCrawler.Utils;
using NCrawler.Interfaces;
using NCrawler.Extensions;
using Polenter.Serialization;

namespace ZCrawler
{
    [Serializable()]
    public class ZAnalysis : IPipelineStep
    {
        public List<ZBasicRule> Rules { get; set; }
        public Action<ZResult> onSingleProcessDone;
        public bool IsPageUrlAnlysis { get; set; }
        public ZAnalysis() { }
        public ZAnalysis(List<ZBasicRule> rs)
        {
            Rules = rs == null ? new List<ZBasicRule>() : rs;
        }
        public ZAnalysis(ZBasicRule rule)
        {
            Rules = new List<ZBasicRule>();
            Rules.Add(rule);
        }
        public void Clean()
        {
            Rules.ForEach(r =>
                {
                    r.reset();
                });
        }
        private void Analysis(HtmlDocument doc, Action<ZResult> res)
        {
            if (!IsPageUrlAnlysis)
            {
                int eofCount = 0;
                ZResult result = null;
                //Seperate Rules By Type
                List<ZBasicRule> crawlRules = Rules.Where(r =>
                {
                    return !r.GetType().IsEquivalentTo(typeof(ZRuleDerived));
                }).ToList();
                List<ZBasicRule> derivedRules = Rules.Where(r =>
                    {
                        return r.GetType().IsEquivalentTo(typeof(ZRuleDerived));
                    }).ToList();

                while (eofCount != crawlRules.Count)
                {
                    eofCount = 0;
                    result = new ZResult();
                    crawlRules.ForEach(r =>
                        {
                            r.SetReachEndTag(!r.Validate());
                            if (!r.IsEnd)
                            {
                                if (!r.GetType().IsEquivalentTo(typeof(ZRuleJson)))
                                    result.Set(r.FieldName, r.Process(doc));
                                else if (r.GetType().IsEquivalentTo(typeof(ZRuleJson)))
                                    result.Set(r.FieldName, r.Process<string>(doc.DocumentNode.OuterHtml));
                            }
                            if (r.IsEnd)
                                eofCount++;
                        }); 
                    derivedRules.ForEach(d =>
                        {
                            if (result == null) result = new ZResult();
                            result.Set(d.FieldName, d.Process<ZResult>(result));
                        });

                    //Identify output result by filter
                    bool willOutputData = true;
                    foreach (ZBasicRule r in Rules)
                    {
                        if (r.IsAnyFilterMatched(result))
                        {
                            willOutputData = false;
                            break;
                        }
                    }
                    if (willOutputData) res(result);
                }
                
                Rules.ForEach(r => r.reset());
            }
            else
            {
                ZResult result = new ZResult();
                Rules.ForEach(r =>
                    {
                        result.Set("url", r.Process(doc));
                        r.reset();
                        res(result);
                    });
            }
        }

        public void Process(Crawler crawler, PropertyBag propertyBag)
        {
            AspectF.Define.
                NotNull(crawler, "crawler").
                NotNull(propertyBag, "propertyBag");

            if (propertyBag.StatusCode != HttpStatusCode.OK)
            {
                return;
            }

            if (!IsHtmlContent(propertyBag.ContentType))
            {
                return;
            }

            HtmlDocument htmlDoc = new HtmlDocument
            {
                //OptionAddDebuggingAttributes = true,
                //OptionAutoCloseOnEnd = true,
                //OptionFixNestedTags = true,
                OptionReadEncoding = true
            };
            using (Stream reader = propertyBag.GetResponse())
            {
                Encoding documentEncoding = htmlDoc.DetectEncoding(reader);
                reader.Seek(0, SeekOrigin.Begin);
                if (!documentEncoding.IsNull())
                {
                    htmlDoc.Load(reader, documentEncoding, true);
                }
                else
                {
                    htmlDoc.Load(reader, true);
                }
            }
            Analysis(htmlDoc, (r) =>
                {
                    onSingleProcessDone(r);
                });
        }
        private static bool IsHtmlContent(string contentType)
        {
            return contentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase)
                || contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
        }
    }
}
